package org.example.engine;

import com.google.common.collect.Lists;
import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.lmax.disruptor.util.DaemonThreadFactory;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.ShortObjectHashMap;
import lombok.extern.slf4j.Slf4j;
import org.example.command.CmdResultCode;
import org.example.command.OrderCmd;
import org.example.model.OrderBook;
import org.example.model.OrderBookImpl;
import org.example.order.CmdType;
import org.example.order.MatchData;
import org.example.service.MQTTClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;


@Service
@Slf4j
public class MatchEngine {

    private final Disruptor<RbCmd> disruptor;
    private final RingBuffer<RbCmd> ringBuffer;
    private static final int BUFFER_SIZE = 1024;

    @Autowired
    MQTTClient mqttClient;

    public MatchEngine() {
        this.disruptor = new Disruptor<RbCmd>(
                new RbCmdFactory(),
                BUFFER_SIZE,
                DaemonThreadFactory.INSTANCE,
                ProducerType.SINGLE,
                new BlockingWaitStrategy()
        );
        ringBuffer = disruptor.getRingBuffer();
        final ExceptionHandler<RbCmd> exceptionHandler = new DisruptorExceptionHandler();
        disruptor.setDefaultExceptionHandler(exceptionHandler);

    }

    @PostConstruct
    public void init() {
        IntObjectHashMap<OrderBook> orderBookMap = initOrderBook();
        MatchHandler matchHandler = new MatchHandler(orderBookMap);
        IntObjectHashMap<List<MatchData>> matcherEventMap = new IntObjectHashMap<>();
        for (int id : Arrays.asList(0,1)) {
            matcherEventMap.put(id, Lists.newArrayList());
        }
        L1PubHandler l1PubHandler = new L1PubHandler(mqttClient, matcherEventMap);
        disruptor.handleEventsWith(matchHandler).then(l1PubHandler);
        disruptor.start();

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                OrderCmd orderCmd = new OrderCmd();
                orderCmd.setType(CmdType.HQ_PUB);
                submit( orderCmd);
            }
        }, 5000, 1000);
    }

    private IntObjectHashMap<OrderBook> initOrderBook() {
        IntObjectHashMap<OrderBook> books = new IntObjectHashMap<>();
        OrderBook orderBook = new OrderBookImpl(0001);
        books.put(0001,orderBook);
        OrderBook orderBook2 = new OrderBookImpl(0002);
        books.put(002,orderBook2);
        return books;
    }


    public void submit(OrderCmd orderCmd) {
        switch ((orderCmd.type)) {
            case NEW_ORDER:
                ringBuffer.publishEvent(orderTranslator,orderCmd);
                return;
            case HQ_PUB:
                ringBuffer.publishEvent(HQ_PUB_TRANSLATOR, orderCmd);
                return;
            default:
                throw new IllegalArgumentException("unknown command type " + orderCmd.type);
        }
    }


    private static final EventTranslatorOneArg<RbCmd,OrderCmd> orderTranslator = (rbCmd, seq, newOrder) -> {
        rbCmd.command = CmdType.NEW_ORDER;
        rbCmd.timestamp = newOrder.timestamp;
        rbCmd.mid = newOrder.mid;
        rbCmd.uid = newOrder.uid;
        rbCmd.code = newOrder.code;
        rbCmd.direction = newOrder.direction;
        rbCmd.price = newOrder.price;
        rbCmd.volume = newOrder.volumn;
        rbCmd.orderType = newOrder.orderType;
        rbCmd.oid = newOrder.oid;
        rbCmd.matchEventList = new LinkedList<>();
        rbCmd.resultCode = CmdResultCode.SUCCESS;
    };

    private static final EventTranslatorOneArg<RbCmd, OrderCmd> HQ_PUB_TRANSLATOR = (rbCmd, seq, hqPub) -> {
        rbCmd.command = CmdType.HQ_PUB;
        rbCmd.resultCode = CmdResultCode.SUCCESS;
    };


    private static class DisruptorExceptionHandler implements ExceptionHandler {

        @Override
        public void handleEventException(Throwable throwable, long l, Object o) {
            log.error("exception {} thrown on {}", throwable, l);
        }

        @Override
        public void handleOnStartException(Throwable throwable) {
            log.error("Disruptor start error {}", throwable);
        }

        @Override
        public void handleOnShutdownException(Throwable throwable) {
            log.error("Disruptor shutdown error {}", throwable);
        }
    }
}
